﻿package net.pandoria
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import fl.core.UIComponent;
	import fl.core.InvalidationType;
	import flash.display.DisplayObject;
	import fl.transitions.*;
	import fl.transitions.easing.*;
	
	import flash.utils.getQualifiedClassName;
	
	import fl.containers.UILoader;
	import fl.controls.ProgressBar;
	
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.Font;
	import flash.text.TextFormat;
	
	public class CustomUILoader extends UIComponent
	{
		////////////////////////////
		// CONSTANTES
		////////////////////////////
		
		// effet par défaut
		private static const DEFAULT_EFFECT : Object = {prop:"alpha",func:Elastic.easeOut,begin:0,end:1,duration:3,useSeconds:true};
		
		////////////////////////////
		// MEMBRES DE CLASSE
		////////////////////////////
		
		//source de l'image à charger
		private var _src 	: String;
		//effets à jouer lors du changement d'état
		//c'est à dire lorsque le composant est "en cours de chargement" ou "en train d'afficher l'image"
		private var _effects : Array;
		//type d'effet que l'on veut jouer
		private var _effect : Object = null;
		//chargeur d'image
		private var _loader : UILoader;
		//espacement entre les éléments
		private var _gap : Number;
		//barre de chargement
		private var _progress : ProgressBar;
		//encart de texte pour affichage du message de chargement
		private var _display : TextField;
		//message à afficher au chargement durant la progression du chargement
		private var _progressMessage : String;
		//message à afficher au chargement à l'échec du chargement
		private var _errorMessage : String;
		//police personnalisée
		private var _font : Font = new Aaaiight() as Font;
		//mise en forme du texte
		private var _format : TextFormat = new TextFormat( _font.fontName, 13, 0x000000, null, true );
		
		
		//composant au sein de l'IDE ?
		private var _isLivePreview:Boolean = (parent != null && getQualifiedClassName(parent) == "fl.livepreview::LivePreviewParent");
		
		////////////////////////////
		// CONSTRUCTEUR DE LA CLASSE
		////////////////////////////
		public function CustomUILoader()
		{
			super();
		}
		
		////////////////////////////
		// SURCHARGES DES METHODES IMPLEMENTEES PAR UICOMPONENT
		////////////////////////////
		
		// METHODE D'INITIALISATION
		override protected function configUI():void
		{
			//faites toujours appel à super.configUI() dans l'implémentation de cette méthode
			//(cela a pour effet, entre autres, de déclencher la suppression automatique de l'avatar)
			super.configUI();
			
			// bloc d'initialisation des éléments du composants
			////////////////////////////////////////////////////
			_loader = new UILoader();
			_progress = new ProgressBar();
			_display = new TextField();
			
			//attribue un nom aux occurences
			_loader.name = "loader";
			_progress.name = "progress";
			_display.name = "display";
			
			//positionne chacun des éléments
			_loader.x = _progress.x = _display.x = gap;
			_loader.y = gap;
			_loader.width = 280;
			_loader.height = 280;
			_loader.alpha = 0;
			_loader.autoLoad = true;
			_loader.maintainAspectRatio = true;
			_loader.scaleContent = true;
			
			_progress.y = 300;
			_progress.width = 280;
			_progress.height = 4;
			
			_display.y = 310;
			_display.width = 280;
			_display.multiline = true;
			_display.autoSize = TextFieldAutoSize.CENTER;
			_display.wordWrap = true;
			_display.embedFonts = true;
			
			_format.letterSpacing = 10;
			
			//relie la barre de chargement au chargeur d'image
			_progress.source = _loader;
			
			// on ajoute les éléments du composant à la liste d'affichage (display list)
			addChild( _loader );
			addChild( _progress );
			addChild( _display );
			
			//initialise les effets sur ce composant
			//si aucun effet personnalisé n'a préalablement été transmis, on l'initialise par défaut
			if ( effect == null )
			{
				var defaultEffect : Object = new Object();
				defaultEffect.prop = DEFAULT_EFFECT.prop;
				defaultEffect.func = DEFAULT_EFFECT.func;
				defaultEffect.begin = DEFAULT_EFFECT.begin;
				defaultEffect.end = DEFAULT_EFFECT.end;
				defaultEffect.duration = DEFAULT_EFFECT.duration;
				defaultEffect.useSeconds = DEFAULT_EFFECT.useSeconds;
				effect = defaultEffect;
			}
			
			//initialise la taille pour les besoin de l'aperçu en direct dans l'IDE (live preview)
			if( _isLivePreview )
				setSize(300,350);
		}
		
		// METHODE DE RENDU A L'AFFICHAGE
		override protected function draw () : void
		{
			//si les éléments sont prêts et qu'il y a matière à afficher
			if ( _loader && ( src != "" ) && ( src != null ) && _progress && _display )
			{
				//positionne automatiquement les éléments et de façon proportionnelle
				//pour tenir dans les dimensions actuelles de ce composant moins l'espacement avec le bord
				var newSubCompWidth : Number = width - ( 2 * gap );
				
				_display.width = newSubCompWidth;
				_display.y = height - _display.height;
				
				_progress.width = newSubCompWidth;
				_progress.y = ( ( height - _display.height - gap ) > ( height / 2 ) ) ? ( height - _display.height - gap ) : ( height / 2 );
				
				_loader.width = newSubCompWidth;
				_loader.height = height - ( 2 * gap );
				_loader.y = gap;
				
				_loader.x = _progress.x = _display.x = gap;
			}
			
			super.draw();
		}
		
		// METHODE DE REDIMENSIONNEMENT
		override public function setSize( w:Number, h:Number ):void
		{			
			invalidate(InvalidationType.SIZE);
			
			super.setSize( w, h );
		}
		
		////////////////////////////
		// METHODES POUR LA LOGIQUE INTERNE DU COMPOSANT (INVALIDATION)
		////////////////////////////
		
		// METHODE DE CREATION DES EFFETS D'INTERPOLATION
		private function initTweens ( targets : Array ) : Array
		{
			//renvoie un tableau répertoriant chacun des effets (tween) créés
			//sur chacune des occurences spécifiées en paramètre
			var lng : int = targets.length;
			
			if ( lng == 0 ) return null;
			
			var effects : Array = new Array();
			var tween : Tween;
			
			for ( var i : int = 0; i < lng ; i++ )
			{
				tween = new Tween ( targets[ i ], effect.prop, effect.func, effect.begin, effect.end, effect.duration, effect.useSeconds );
				tween.stop();
				effects.push( tween );
			}
			
			return effects;
		}
		
		//fait en sorte que si le UILoader apparaît, la ProgressBar et le TextField disparaissent
		//ou vice-versa en fonction du paramètre reverse
		private function playTweens( tweens : Array, reverse : Boolean = false ) : void
		{
			var lng : int = tweens.length;
			var loaderNext : int = (( !reverse ) ? effect.end : effect.begin );
			var othersNext : int = (( !reverse ) ? effect.begin : effect.end );
			
			//fait aussi en sorte que le tween ne soit pas joué dans l'aperçu en direct de l'IDE
			//car cela engendre des bugs de l'aperçu
			for ( var i : int = 0; i < lng; i++)
			{
				if ( tweens[ i ].obj.name == "loader" )
				{
					if ( !_isLivePreview )
						( tweens[ i ] as Tween ).continueTo( loaderNext, 3 );
					else
					{
						( !reverse ) ? ( tweens[ i ] as Tween ).fforward() : ( tweens[ i ] as Tween ).rewind();
					}
				}
				else
				{
					if ( !_isLivePreview )
						( tweens[ i ] as Tween ).continueTo( othersNext, 3 );
					else
					{
						( reverse ) ? ( tweens[ i ] as Tween ).fforward() : ( tweens[ i ] as Tween ).rewind();
					}
				}
			}
		}
		
		///// méthodes pour gérer la mise à jour du chemin source de l'image
		// met le composant en mode "chargement"
		private function updateSRC ( evt : TweenEvent = null ) : void
		{
			//joue l'interpolation des effets jusqu'à disparition du UILoader
			//et apparition de la ProgressBar et du TextField
			playTweens( _effects, true );
			//change la source du UILoader
			_loader.source = _src;
			//affiche le message de chargement
			_display.text = _progressMessage;
			_display.setTextFormat( _format );
			
			//ajoute un écouteur pour le moment où le UILoader a fini de charger
			_loader.addEventListener(Event.COMPLETE, completeSRC );
			_loader.addEventListener(IOErrorEvent.IO_ERROR, errorSRC );
		}
		//spécifie que le chargement a échoué
		private function errorSRC( evt: Event ) : void
		{
			// vide le texte du message de chargement
			_display.text = _errorMessage;
			_display.setTextFormat( _format );
			
			//suppression des écouteurs
			_loader.removeEventListener(Event.COMPLETE, completeSRC );
			_loader.removeEventListener(IOErrorEvent.IO_ERROR, errorSRC );
		}
		//met le composant en mode "affichage" lorsque le chargement est complet
		private function completeSRC( evt: Event ) : void
		{
			//joue l'interpolation des effets jusqu'à apparition du UIloader
			//et disparition de la ProgressBar et du TextField
			playTweens( _effects );
			// vide le texte du message de chargement
			_display.text = "";
			_display.setTextFormat( _format );
			
			//suppression des écouteurs
			_loader.removeEventListener(Event.COMPLETE, completeSRC );
			_loader.removeEventListener(IOErrorEvent.IO_ERROR, errorSRC );
		}
		
		////////////////////////////
		// PROPRIETES INSPECTABLES
		////////////////////////////
		
		// renvoie la source
		[Inspectable(type="String",name="source")]
		public function get src () : String
		{
			return _loader.source as String;
		}
		
		// redéfini la source en notifiant la mise à jour de l'état du composant
		public function set src ( value : String ) : void
		{
			if ( value != null && value != "" )
			{
				_src = value;
				updateSRC();
			}
		}
		
		// renvoie la source
		[Inspectable(type="Number",defaultValue=10,name="espacement")]
		public function get gap () : Number
		{
			return _gap;
		}
		
		// redéfini la source en notifiant la mise à jour de l'état du composant
		public function set gap ( value : Number ) : void
		{
			if ( !isNaN( value ) )
			{
				_gap = value;
				invalidate(InvalidationType.SIZE);
			}
		}
		
		// renvoie le message de progression
		[Inspectable(type="String",defaultValue="chargement...",name="message de progression")]
		public function get progressMessage () : String
		{
			return _progressMessage;
		}
		
		// redéfini le message de progression
		public function set progressMessage ( value : String ) : void
		{
			_progressMessage = value;
		}
		
		// renvoie le message d'erreur
		[Inspectable(type="String",defaultValue="erreur de chargement",name="message d'erreur")]
		public function get errorMessage () : String
		{
			return _errorMessage;
		}
		
		// redéfini le message d'erreur
		public function set errorMessage ( value : String ) : void
		{
			_errorMessage = value;
		}
		
		////////////////////////////
		// PROPRIETES MODIFIABLES PROGRAMMATIQUEMENT
		////////////////////////////
		
		// renvoie l'effet
		public function get effect () : Object
		{
			return _effect;
		}
		
		// redéfini l'effet joué au chagement d'état du composant et réinitialise les tweens
		public function set effect ( value : Object ) : void
		{
			//vérifie que le programmeur n'oublie aucun paramètres et que ceux-ci sont du type attendu
			if ( value.hasOwnProperty("prop") && value.hasOwnProperty("func") && value.hasOwnProperty("duration") && value.hasOwnProperty("useSeconds") )
			{
				if ( value.prop is String && value.func is Function && value.duration is int && value.useSeconds is Boolean )
				{
					value.begin = 0;
					value.end = 1;
					_effect = value;
					_effects = initTweens ( [ _loader, _progress, _display ] );
				}
			}
		}
		
		////////////////////////////
		// PROPRIETES D'ETAT
		////////////////////////////
		
		// renvoie le pourcentage des données de l'image chargées
		public function get percentLoaded () : Number
		{
			return ( _loader.percentLoaded );
		}
		
		// permet de déterminer si le composant est d'ores et déjà en train de charger une image
		public function get isLoading ( ) : Boolean
		{
			return ( percentLoaded < 100 );
		}
		
		override public function set width( value:Number ):void
        {
            setSize( value, height );
        }
		
		override public function set height( value:Number ):void
        {
            setSize( width, value );
        }
	}
}